#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

const int defaultcap = 5;

template<typename T>
class BlockQueue
{
private:
    bool isfull() { return _capacity <= _blockq.size(); }

    bool isempty() { return _blockq.empty(); }
public:
    BlockQueue(int cap = defaultcap)
        :_capacity(cap), _consumer_sleep(0), _producer_sleep(0)
    {
        // 初始化锁和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_consumer, nullptr);
        pthread_cond_init(&_producer, nullptr);
    }

    ~BlockQueue()
    {
        // 销毁锁和条件变量
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumer);
        pthread_cond_destroy(&_producer);
    }

    void emplace(const T& data)
    {
        // 1.插入数据时先加锁
        pthread_mutex_lock(&_mutex);

        // 2.如果阻塞队列满了，就在生产者条件变量下等待
        while(isfull()){
            _producer_sleep++;
            pthread_cond_wait(&_producer, &_mutex);
            _producer_sleep--;
        }

        // 3.阻塞队列没满就直接入队列
        _blockq.emplace(data);

        // 生产者生成一个数据之后，就可以唤醒消费者了
        if(_consumer_sleep > 0){
            pthread_cond_signal(&_consumer);
        }

        // 4.解锁
        pthread_mutex_unlock(&_mutex);
    }

    T pop()
    {
        // 1.访问数据先加锁
        pthread_mutex_lock(&_mutex);

        // 2.如果阻塞队列为空，则消费者的条件变量下等待
        while(isempty()){
            _consumer_sleep++;
            //std::cout << "队列为空，阻塞 " << _consumer_sleep << std::endl;
            pthread_cond_wait(&_consumer, &_mutex);
            _consumer_sleep--;
        }

        // 3.如果有数据就直接读取
        T ret = _blockq.front();
        _blockq.pop();

        // 消费者读取一个数据就有空间了，此时就可以唤醒生产者
        if(_producer_sleep > 0){
            pthread_cond_signal(&_producer);
        }

        // 4.解锁
        pthread_mutex_unlock(&_mutex);

        // 5.返回数据
        return ret;
    }
private:
    std::queue<T> _blockq;
    int _capacity;

    pthread_mutex_t _mutex;
    pthread_cond_t _producer;
    pthread_cond_t _consumer;

    int _consumer_sleep;
    int _producer_sleep;
};